/*
 * pgp-mpi-openssl.c
 *	  使用 OpenSSL BIGNUM 代码的 OpenPGP MPI 函数。
 *
 * Copyright (c) 2005 Marko Kreen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *	  notice, this list of条件和以下免责声明。
 * 2. Redistributions in binary form must reproduce the above copyright
 *	  notice, this list of条件和以下免责声明在文档和/或其他提供的材料
 * 与分发一起。
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, indirect, INCIDENTAL, SPECIAL, EXEMPLARY, 或后果
 * 损害（包括但不限于替代商品或服务的采购；丧失使用、数据或利润；或业务中断）
 * 无论因何原因以及在任何责任理论下，无论是合同、严格
 * 责任或侵权行为（包括疏忽或其他）以任何方式产生
 * 出于对本软件的使用，即使在被告知有可能发生
 * 这样的损害。
 *
 * contrib/pgcrypto/pgp-mpi-openssl.c
 */
#include "postgres.h"

#include <openssl/bn.h>

#include "pgp.h"
#include "px.h"

static BIGNUM *
fc_mpi_to_bn(PGP_MPI *fc_n)
{
	BIGNUM	   *fc_bn = BN_bin2bn(fc_n->data, fc_n->bytes, NULL);

	if (!fc_bn)
		return NULL;
	if (BN_num_bits(fc_bn) != fc_n->bits)
	{
		px_debug("mpi_to_bn: bignum conversion failed: mpi=%d, bn=%d",
				 fc_n->bits, BN_num_bits(fc_bn));
		BN_clear_free(fc_bn);
		return NULL;
	}
	return fc_bn;
}

static PGP_MPI *
fc_bn_to_mpi(BIGNUM *fc_bn)
{
	int			fc_res;
	PGP_MPI    *fc_n;

	fc_res = pgp_mpi_alloc(BN_num_bits(fc_bn), &fc_n);
	if (fc_res < 0)
		return NULL;

	if (BN_num_bytes(fc_bn) != fc_n->bytes)
	{
		px_debug("bn_to_mpi: bignum conversion failed: bn=%d, mpi=%d",
				 BN_num_bytes(fc_bn), fc_n->bytes);
		pgp_mpi_free(fc_n);
		return NULL;
	}
	BN_bn2bin(fc_bn, fc_n->data);
	return fc_n;
}

/*
 * 决定随机组分 k 的位数
 *
 * 它应该与用于签名的 p 在相同范围内（该功能
 * 已被弃用），但对于加密可以小得多。
 *
 * 在我进一步研究之前，我只是模仿 gpg 的行为。
 * 它有一个特殊的映射表，对于值 <= 5120，
 * 超过这个值，它使用“任意高数字”。以下
 * 算法在 gpg 值上游走 10-70 位。对于
 * 更大的 p，它使用 gpg 的算法。
 *
 * 关键是 - 如果 k 变大，加密会
 * 非常慢。对解密而言则无关紧要。
 */
static int fc_decide_k_bits(int fc_p_bits)
{
	if (fc_p_bits <= 5120)
		return fc_p_bits / 10 + 160;
	else
		return (fc_p_bits / 8 + 200) * 3 / 2;
}

int pgp_elgamal_encrypt(PGP_PubKey *fc_pk, PGP_MPI *fc__m,
					PGP_MPI **fc_c1_p, PGP_MPI **fc_c2_p)
{
	int			fc_res = PXE_PGP_MATH_FAILED;
	int			fc_k_bits;
	BIGNUM	   *fc_m = fc_mpi_to_bn(fc__m);
	BIGNUM	   *fc_p = fc_mpi_to_bn(fc_pk->pub.elg.p);
	BIGNUM	   *fc_g = fc_mpi_to_bn(fc_pk->pub.elg.g);
	BIGNUM	   *fc_y = fc_mpi_to_bn(fc_pk->pub.elg.y);
	BIGNUM	   *fc_k = BN_new();
	BIGNUM	   *fc_yk = BN_new();
	BIGNUM	   *fc_c1 = BN_new();
	BIGNUM	   *fc_c2 = BN_new();
	BN_CTX	   *fc_tmp = BN_CTX_new();

	if (!fc_m || !fc_p || !fc_g || !fc_y || !fc_k || !fc_yk || !fc_c1 || !fc_c2 || !fc_tmp)
		goto err;

	/*
	 * 生成 k
	 */
	fc_k_bits = fc_decide_k_bits(BN_num_bits(fc_p));
	if (!BN_rand(fc_k, fc_k_bits, 0, 0))
		goto err;

	/*
	 * c1 = g^k c2 = m * y^k
	 */
	if (!BN_mod_exp(fc_c1, fc_g, fc_k, fc_p, fc_tmp))
		goto err;
	if (!BN_mod_exp(fc_yk, fc_y, fc_k, fc_p, fc_tmp))
		goto err;
	if (!BN_mod_mul(fc_c2, fc_m, fc_yk, fc_p, fc_tmp))
		goto err;

	/* 结果 */
	*fc_c1_p = fc_bn_to_mpi(fc_c1);
	*fc_c2_p = fc_bn_to_mpi(fc_c2);
	if (*fc_c1_p && *fc_c2_p)
		fc_res = 0;
err:
	if (fc_tmp)
		BN_CTX_free(fc_tmp);
	if (fc_c2)
		BN_clear_free(fc_c2);
	if (fc_c1)
		BN_clear_free(fc_c1);
	if (fc_yk)
		BN_clear_free(fc_yk);
	if (fc_k)
		BN_clear_free(fc_k);
	if (fc_y)
		BN_clear_free(fc_y);
	if (fc_g)
		BN_clear_free(fc_g);
	if (fc_p)
		BN_clear_free(fc_p);
	if (fc_m)
		BN_clear_free(fc_m);
	return fc_res;
}

int pgp_elgamal_decrypt(PGP_PubKey *fc_pk, PGP_MPI *fc__c1, PGP_MPI *fc__c2,
					PGP_MPI **fc_msg_p)
{
	int			fc_res = PXE_PGP_MATH_FAILED;
	BIGNUM	   *fc_c1 = fc_mpi_to_bn(fc__c1);
	BIGNUM	   *fc_c2 = fc_mpi_to_bn(fc__c2);
	BIGNUM	   *fc_p = fc_mpi_to_bn(fc_pk->pub.elg.p);
	BIGNUM	   *fc_x = fc_mpi_to_bn(fc_pk->sec.elg.x);
	BIGNUM	   *fc_c1x = BN_new();
	BIGNUM	   *fc_div = BN_new();
	BIGNUM	   *fc_m = BN_new();
	BN_CTX	   *fc_tmp = BN_CTX_new();

	if (!fc_c1 || !fc_c2 || !fc_p || !fc_x || !fc_c1x || !fc_div || !fc_m || !fc_tmp)
		goto err;

	/*
	 * m = c2 / (c1^x)
	 */
	if (!BN_mod_exp(fc_c1x, fc_c1, fc_x, fc_p, fc_tmp))
		goto err;
	if (!BN_mod_inverse(fc_div, fc_c1x, fc_p, fc_tmp))
		goto err;
	if (!BN_mod_mul(fc_m, fc_c2, fc_div, fc_p, fc_tmp))
		goto err;

	/* 结果 */
	*fc_msg_p = fc_bn_to_mpi(fc_m);
	if (*fc_msg_p)
		fc_res = 0;
err:
	if (fc_tmp)
		BN_CTX_free(fc_tmp);
	if (fc_m)
		BN_clear_free(fc_m);
	if (fc_div)
		BN_clear_free(fc_div);
	if (fc_c1x)
		BN_clear_free(fc_c1x);
	if (fc_x)
		BN_clear_free(fc_x);
	if (fc_p)
		BN_clear_free(fc_p);
	if (fc_c2)
		BN_clear_free(fc_c2);
	if (fc_c1)
		BN_clear_free(fc_c1);
	return fc_res;
}

int pgp_rsa_encrypt(PGP_PubKey *fc_pk, PGP_MPI *fc__m, PGP_MPI **fc_c_p)
{
	int			fc_res = PXE_PGP_MATH_FAILED;
	BIGNUM	   *fc_m = fc_mpi_to_bn(fc__m);
	BIGNUM	   *fc_e = fc_mpi_to_bn(fc_pk->pub.rsa.e);
	BIGNUM	   *fc_n = fc_mpi_to_bn(fc_pk->pub.rsa.n);
	BIGNUM	   *fc_c = BN_new();
	BN_CTX	   *fc_tmp = BN_CTX_new();

	if (!fc_m || !fc_e || !fc_n || !fc_c || !fc_tmp)
		goto err;

	/*
	 * c = m ^ e
	 */
	if (!BN_mod_exp(fc_c, fc_m, fc_e, fc_n, fc_tmp))
		goto err;

	*fc_c_p = fc_bn_to_mpi(fc_c);
	if (*fc_c_p)
		fc_res = 0;
err:
	if (fc_tmp)
		BN_CTX_free(fc_tmp);
	if (fc_c)
		BN_clear_free(fc_c);
	if (fc_n)
		BN_clear_free(fc_n);
	if (fc_e)
		BN_clear_free(fc_e);
	if (fc_m)
		BN_clear_free(fc_m);
	return fc_res;
}

int pgp_rsa_decrypt(PGP_PubKey *fc_pk, PGP_MPI *fc__c, PGP_MPI **fc_m_p)
{
	int			fc_res = PXE_PGP_MATH_FAILED;
	BIGNUM	   *fc_c = fc_mpi_to_bn(fc__c);
	BIGNUM	   *fc_d = fc_mpi_to_bn(fc_pk->sec.rsa.d);
	BIGNUM	   *fc_n = fc_mpi_to_bn(fc_pk->pub.rsa.n);
	BIGNUM	   *fc_m = BN_new();
	BN_CTX	   *fc_tmp = BN_CTX_new();

	if (!fc_m || !fc_d || !fc_n || !fc_c || !fc_tmp)
		goto err;

	/*
	 * m = c ^ d
	 */
	if (!BN_mod_exp(fc_m, fc_c, fc_d, fc_n, fc_tmp))
		goto err;

	*fc_m_p = fc_bn_to_mpi(fc_m);
	if (*fc_m_p)
		fc_res = 0;
err:
	if (fc_tmp)
		BN_CTX_free(fc_tmp);
	if (fc_m)
		BN_clear_free(fc_m);
	if (fc_n)
		BN_clear_free(fc_n);
	if (fc_d)
		BN_clear_free(fc_d);
	if (fc_c)
		BN_clear_free(fc_c);
	return fc_res;
}
